package org.boticelli.logsearch;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.boticelli.dao.LogDAO;
import org.boticelli.model.LogEntry;
import org.boticelli.util.DateUtil;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ReindexController
{
    protected static Logger log = Logger.getLogger(ReindexController.class);

    private SessionFactory sessionFactory;

    private LogDAO logDAO;

    private LogIndexer logIndexer;

    private ReIndexThread thread;

    private Object lock = new Object();

    @Autowired(required = true)
    public void setLogDAO(LogDAO logDAO)
    {
        this.logDAO = logDAO;
    }

    @Autowired(required = true)
    public void setLogIndexer(LogIndexer logIndexer)
    {
        this.logIndexer = logIndexer;
    }

    @RequestMapping("/admin/reindex")
    public ModelAndView reindex(HttpServletRequest request, HttpServletResponse response,
            @RequestParam(value = "confirm", required = false) Boolean confirm, 
            @RequestParam(value = "minDay", required = false) String minDay, 
            @RequestParam(value = "maxDay", required = false) String maxDay) throws IOException
    {
        synchronized (lock)
        {
            if (thread == null)
            {
                if (confirm == null || !confirm.booleanValue())
                {
                    int minDayinDB = ((Integer) sessionFactory.openSession().createQuery("select min(l.day) from LogEntry l")
                            .uniqueResult()).intValue();
                    ModelAndView mav = new ModelAndView("confirm-reindex");
                    mav.addObject("minDay", DateUtil.toYYYYMMDD( DateUtil.fromDaySinceEpoch(minDayinDB)));
                    mav.addObject("maxDay", DateUtil.toYYYYMMDD( Calendar.getInstance()));
                    return mav;
                }

                thread = new ReIndexThread( DateUtil.daySinceEpoch(DateUtil.getCalendarFor(minDay)),DateUtil.daySinceEpoch(DateUtil.getCalendarFor(maxDay)));
                thread.start();

                response.sendRedirect(request.getContextPath() + "/app/admin/reindex");
                return null;
            }
            else
            {
                ModelAndView mav = new ModelAndView("reindex_status");
                mav.addObject("status", thread.getPercentage());
                return mav;
            }
        }
    }

    @Autowired(required = true)
    public void setSessionFactory(SessionFactory sessionFactory)
    {
        this.sessionFactory = sessionFactory;
    }

    public class ReIndexThread
            extends Thread
    {

        private PerDayIndexer perDayIndexer=new PerDayIndexer();
        private int maxDay;
        private int minDay;

        public ReIndexThread(int minDay, int maxDay)
        {
            super("ReIndexThread");
            this.minDay = minDay;
            this.maxDay = maxDay;
            setPriority(Thread.MIN_PRIORITY);
        }

        public int getPercentage()
        {
            return perDayIndexer.getPercentage();
        }
        
        @Override
        public void run()
        {
            Session session = null;
            boolean participate = false;
            try
            {
                if (TransactionSynchronizationManager.hasResource(sessionFactory))
                {
                    // Do not modify the Session: just set the participate flag.
                    participate = true;
                }
                else
                {
                    session = SessionFactoryUtils.getSession(sessionFactory, true);
                    session.setFlushMode(FlushMode.ALWAYS);
                    TransactionSynchronizationManager.bindResource(sessionFactory,
                            new SessionHolder(session));
                }

                session = ((SessionHolder) TransactionSynchronizationManager
                        .getResource(sessionFactory)).getSession();

                log.info("Indexing day " + minDay + " to " + maxDay);
                
                perDayIndexer.setLimits(minDay,maxDay);
                logIndexer.index(session,perDayIndexer, true);
                
            }
            catch (Exception e)
            {
                log.error("Error during updateCommand: ", e);
            }
            finally
            {
                synchronized (lock)
                {
                    thread = null;
                }

                if (!participate && session != null)
                {
                    SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
                            .unbindResource(sessionFactory);
                    SessionFactoryUtils.closeSession(session);
                }
            }
        }
    }

    private class PerDayIndexer
            implements IndexLogProvider
    {
        private int minDay, maxDay=1;

        private int day;

        public void setLimits(int minDay, int maxDay)
        {
            this.minDay = minDay;
            this.maxDay = maxDay;
            this.day = minDay;
        }

        public List<LogEntry> getNextBlock()
        {
            int day=nextDay();
            
            if (day > maxDay)
            {
                return null;
            }
            
            if (log.isInfoEnabled() && (day & 15) == 0)
            {
                Calendar cal = Calendar.getInstance();
                cal.set(Calendar.DAY_OF_MONTH, 1);
                cal.set(Calendar.MONTH, 0);
                cal.set(Calendar.YEAR, 1970);
                cal.add(Calendar.DAY_OF_MONTH, day);
                log.info("Indexing day " + day + " = " + cal.getTime());
            }

            return logDAO.findByDay(day);
        }

        private synchronized int nextDay()
        {
            return this.day++;
        }

        public synchronized int getPercentage()
        {
            return (int) ((day - minDay) * 100.0 / (maxDay - minDay));
        }
    }
}
